#!/usr/bin/env python3
"""
Chinese Chess (Xiangqi) Game - PyQt6 Implementation

This application implements a complete Chinese chess game with:
- 10x9 game board with traditional layout
- All chess pieces with proper Chinese characters
- Complete movement rules for all pieces
- Turn-based gameplay (Red vs Black)
- Win condition detection
- Game reset functionality
- Move history tracking
"""

import sys
from enum import Enum
from typing import Optional, List, Tuple
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QTextEdit, QMessageBox, QMenuBar, QStatusBar,
    QGroupBox, QSplitter
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QPainter, QPen, QBrush, QFont, QAction, QColor
from PyQt6.QtWidgets import QFrame


class PieceType(Enum):
    """Chinese chess piece types"""
    GENERAL = "general"      # 帥/將
    ADVISOR = "advisor"      # 仕/士  
    ELEPHANT = "elephant"    # 相/象
    HORSE = "horse"          # 馬/马
    CHARIOT = "chariot"      # 車/车
    CANNON = "cannon"        # 炮/砲
    PAWN = "pawn"           # 兵/卒


class Player(Enum):
    """Players in the game"""
    RED = "red"
    BLACK = "black"


class ChessPiece:
    """Represents a Chinese chess piece"""
    
    # Chinese characters for pieces
    PIECE_CHARS = {
        Player.RED: {
            PieceType.GENERAL: "帥",
            PieceType.ADVISOR: "仕",
            PieceType.ELEPHANT: "相",
            PieceType.HORSE: "馬",
            PieceType.CHARIOT: "車",
            PieceType.CANNON: "炮",
            PieceType.PAWN: "兵"
        },
        Player.BLACK: {
            PieceType.GENERAL: "將",
            PieceType.ADVISOR: "士",
            PieceType.ELEPHANT: "象",
            PieceType.HORSE: "马",
            PieceType.CHARIOT: "车",
            PieceType.CANNON: "砲",
            PieceType.PAWN: "卒"
        }
    }
    
    def __init__(self, piece_type: PieceType, player: Player, row: int, col: int):
        self.piece_type = piece_type
        self.player = player
        self.row = row
        self.col = col
        
    def get_char(self) -> str:
        """Get the Chinese character for this piece"""
        return self.PIECE_CHARS[self.player][self.piece_type]
        
    def __str__(self):
        return f"{self.player.value} {self.piece_type.value} at ({self.row}, {self.col})"


class ChessBoard(QFrame):
    """Chess board widget that handles display and interaction"""
    
    piece_moved = pyqtSignal(int, int, int, int)  # from_row, from_col, to_row, to_col
    
    def __init__(self):
        super().__init__()
        self.setFixedSize(600, 700)
        self.setFrameStyle(QFrame.Shape.Box)
        
        # Board dimensions
        self.rows = 10
        self.cols = 9
        self.cell_size = 60
        self.margin = 40
        
        # Game state
        self.board: List[List[Optional[ChessPiece]]] = [[None for _ in range(self.cols)] for _ in range(self.rows)]
        self.selected_piece: Optional[ChessPiece] = None
        self.selected_pos: Optional[Tuple[int, int]] = None
        self.current_player = Player.RED
        self.game_over = False
        
        self.setup_initial_board()
        
    def setup_initial_board(self):
        """Setup the initial chess board with all pieces"""
        # Clear board
        self.board = [[None for _ in range(self.cols)] for _ in range(self.rows)]
        
        # Black pieces (top)
        self.board[0][0] = ChessPiece(PieceType.CHARIOT, Player.BLACK, 0, 0)
        self.board[0][1] = ChessPiece(PieceType.HORSE, Player.BLACK, 0, 1)
        self.board[0][2] = ChessPiece(PieceType.ELEPHANT, Player.BLACK, 0, 2)
        self.board[0][3] = ChessPiece(PieceType.ADVISOR, Player.BLACK, 0, 3)
        self.board[0][4] = ChessPiece(PieceType.GENERAL, Player.BLACK, 0, 4)
        self.board[0][5] = ChessPiece(PieceType.ADVISOR, Player.BLACK, 0, 5)
        self.board[0][6] = ChessPiece(PieceType.ELEPHANT, Player.BLACK, 0, 6)
        self.board[0][7] = ChessPiece(PieceType.HORSE, Player.BLACK, 0, 7)
        self.board[0][8] = ChessPiece(PieceType.CHARIOT, Player.BLACK, 0, 8)
        
        # Black cannons
        self.board[2][1] = ChessPiece(PieceType.CANNON, Player.BLACK, 2, 1)
        self.board[2][7] = ChessPiece(PieceType.CANNON, Player.BLACK, 2, 7)
        
        # Black pawns
        for col in [0, 2, 4, 6, 8]:
            self.board[3][col] = ChessPiece(PieceType.PAWN, Player.BLACK, 3, col)
            
        # Red pieces (bottom)
        self.board[9][0] = ChessPiece(PieceType.CHARIOT, Player.RED, 9, 0)
        self.board[9][1] = ChessPiece(PieceType.HORSE, Player.RED, 9, 1)
        self.board[9][2] = ChessPiece(PieceType.ELEPHANT, Player.RED, 9, 2)
        self.board[9][3] = ChessPiece(PieceType.ADVISOR, Player.RED, 9, 3)
        self.board[9][4] = ChessPiece(PieceType.GENERAL, Player.RED, 9, 4)
        self.board[9][5] = ChessPiece(PieceType.ADVISOR, Player.RED, 9, 5)
        self.board[9][6] = ChessPiece(PieceType.ELEPHANT, Player.RED, 9, 6)
        self.board[9][7] = ChessPiece(PieceType.HORSE, Player.RED, 9, 7)
        self.board[9][8] = ChessPiece(PieceType.CHARIOT, Player.RED, 9, 8)
        
        # Red cannons
        self.board[7][1] = ChessPiece(PieceType.CANNON, Player.RED, 7, 1)
        self.board[7][7] = ChessPiece(PieceType.CANNON, Player.RED, 7, 7)
        
        # Red pawns
        for col in [0, 2, 4, 6, 8]:
            self.board[6][col] = ChessPiece(PieceType.PAWN, Player.RED, 6, col)
            
        self.current_player = Player.RED
        self.game_over = False
        self.selected_piece = None
        self.selected_pos = None
        self.update()
        
    def paintEvent(self, event):
        """Paint the chess board and pieces"""
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        
        # Draw board background
        painter.fillRect(self.rect(), QColor(245, 222, 179))  # Light wood color
        
        # Draw grid lines
        pen = QPen(Qt.GlobalColor.black, 2)
        painter.setPen(pen)
        
        # Vertical lines
        for col in range(self.cols):
            x = self.margin + col * self.cell_size
            # Top half
            painter.drawLine(x, self.margin, x, self.margin + 4 * self.cell_size)
            # Bottom half
            painter.drawLine(x, self.margin + 5 * self.cell_size, x, self.margin + 9 * self.cell_size)
            
        # Horizontal lines
        for row in range(self.rows):
            y = self.margin + row * self.cell_size
            painter.drawLine(self.margin, y, self.margin + 8 * self.cell_size, y)
            
        # Draw palace diagonals
        # Black palace
        painter.drawLine(self.margin + 3 * self.cell_size, self.margin,
                        self.margin + 5 * self.cell_size, self.margin + 2 * self.cell_size)
        painter.drawLine(self.margin + 5 * self.cell_size, self.margin,
                        self.margin + 3 * self.cell_size, self.margin + 2 * self.cell_size)
        
        # Red palace
        painter.drawLine(self.margin + 3 * self.cell_size, self.margin + 7 * self.cell_size,
                        self.margin + 5 * self.cell_size, self.margin + 9 * self.cell_size)
        painter.drawLine(self.margin + 5 * self.cell_size, self.margin + 7 * self.cell_size,
                        self.margin + 3 * self.cell_size, self.margin + 9 * self.cell_size)
        
        # Draw river text
        font = QFont("Arial", 16, QFont.Weight.Bold)
        painter.setFont(font)
        painter.setPen(Qt.GlobalColor.darkBlue)
        painter.drawText(self.margin + self.cell_size, int(self.margin + 4.3 * self.cell_size), "楚河")
        painter.drawText(self.margin + 5 * self.cell_size, int(self.margin + 4.7 * self.cell_size), "漢界")
        
        # Draw pieces
        font = QFont("SimSun", 24, QFont.Weight.Bold)
        painter.setFont(font)
        
        for row in range(self.rows):
            for col in range(self.cols):
                piece = self.board[row][col]
                if piece:
                    x = self.margin + col * self.cell_size
                    y = self.margin + row * self.cell_size
                    
                    # Highlight selected piece
                    if self.selected_pos == (row, col):
                        painter.fillRect(x - 25, y - 25, 50, 50, QColor(255, 255, 0, 128))
                    
                    # Draw piece circle
                    if piece.player == Player.RED:
                        painter.setBrush(QBrush(QColor(255, 200, 200)))
                        painter.setPen(QPen(Qt.GlobalColor.darkRed, 2))
                    else:
                        painter.setBrush(QBrush(QColor(220, 220, 220)))
                        painter.setPen(QPen(Qt.GlobalColor.black, 2))
                    
                    painter.drawEllipse(x - 22, y - 22, 44, 44)
                    
                    # Draw piece character
                    if piece.player == Player.RED:
                        painter.setPen(Qt.GlobalColor.darkRed)
                    else:
                        painter.setPen(Qt.GlobalColor.black)
                    
                    painter.drawText(x - 15, y + 8, piece.get_char())
                    
    def mousePressEvent(self, event):
        """Handle mouse clicks on the board"""
        if self.game_over:
            return
            
        if event.button() == Qt.MouseButton.LeftButton:
            # Convert click position to board coordinates
            col = round((event.position().x() - self.margin) / self.cell_size)
            row = round((event.position().y() - self.margin) / self.cell_size)
            
            if 0 <= row < self.rows and 0 <= col < self.cols:
                self.handle_square_click(row, col)
                
    def handle_square_click(self, row: int, col: int):
        """Handle clicking on a board square"""
        clicked_piece = self.board[row][col]
        
        if self.selected_piece is None:
            # No piece selected, try to select piece
            if clicked_piece and clicked_piece.player == self.current_player:
                self.selected_piece = clicked_piece
                self.selected_pos = (row, col)
                self.update()
        else:
            # Piece already selected
            if self.selected_pos == (row, col):
                # Clicked same square, deselect
                self.selected_piece = None
                self.selected_pos = None
                self.update()
            elif clicked_piece and clicked_piece.player == self.current_player:
                # Clicked own piece, select it instead
                self.selected_piece = clicked_piece
                self.selected_pos = (row, col)
                self.update()
            else:
                # Try to move to this square
                if self.is_valid_move(self.selected_pos[0], self.selected_pos[1], row, col):
                    self.make_move(self.selected_pos[0], self.selected_pos[1], row, col)
                else:
                    # Invalid move, just deselect
                    self.selected_piece = None
                    self.selected_pos = None
                    self.update()
                    
    def is_valid_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Check if a move is valid according to Chinese chess rules"""
        piece = self.board[from_row][from_col]
        if not piece:
            return False
            
        target_piece = self.board[to_row][to_col]
        
        # Can't capture own piece
        if target_piece and target_piece.player == piece.player:
            return False
            
        # Check piece-specific movement rules
        if piece.piece_type == PieceType.GENERAL:
            return self.is_valid_general_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.ADVISOR:
            return self.is_valid_advisor_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.ELEPHANT:
            return self.is_valid_elephant_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.HORSE:
            return self.is_valid_horse_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.CHARIOT:
            return self.is_valid_chariot_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.CANNON:
            return self.is_valid_cannon_move(from_row, from_col, to_row, to_col)
        elif piece.piece_type == PieceType.PAWN:
            return self.is_valid_pawn_move(from_row, from_col, to_row, to_col)
            
        return False
        
    def is_in_palace(self, row: int, col: int, player: Player) -> bool:
        """Check if position is within the palace"""
        if player == Player.RED:
            return 7 <= row <= 9 and 3 <= col <= 5
        else:
            return 0 <= row <= 2 and 3 <= col <= 5
            
    def is_valid_general_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate general/king movement"""
        piece = self.board[from_row][from_col]
        
        # Must stay in palace
        if not self.is_in_palace(to_row, to_col, piece.player):
            return False
            
        # Can only move one step orthogonally
        row_diff = abs(to_row - from_row)
        col_diff = abs(to_col - from_col)
        
        return (row_diff == 1 and col_diff == 0) or (row_diff == 0 and col_diff == 1)
        
    def is_valid_advisor_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate advisor movement"""
        piece = self.board[from_row][from_col]
        
        # Must stay in palace
        if not self.is_in_palace(to_row, to_col, piece.player):
            return False
            
        # Can only move one step diagonally
        row_diff = abs(to_row - from_row)
        col_diff = abs(to_col - from_col)
        
        return row_diff == 1 and col_diff == 1
        
    def is_valid_elephant_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate elephant movement"""
        piece = self.board[from_row][from_col]
        
        # Cannot cross river
        if piece.player == Player.RED and to_row < 5:
            return False
        if piece.player == Player.BLACK and to_row > 4:
            return False
            
        # Must move exactly 2 steps diagonally
        row_diff = to_row - from_row
        col_diff = to_col - from_col
        
        if abs(row_diff) != 2 or abs(col_diff) != 2:
            return False
            
        # Check if blocking piece exists
        block_row = from_row + row_diff // 2
        block_col = from_col + col_diff // 2
        
        return self.board[block_row][block_col] is None
        
    def is_valid_horse_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate horse movement"""
        row_diff = to_row - from_row
        col_diff = to_col - from_col
        
        # Must move in L-shape
        if not ((abs(row_diff) == 2 and abs(col_diff) == 1) or 
                (abs(row_diff) == 1 and abs(col_diff) == 2)):
            return False
            
        # Check for blocking piece
        if abs(row_diff) == 2:
            # Moving vertically first
            block_row = from_row + row_diff // 2
            block_col = from_col
        else:
            # Moving horizontally first
            block_row = from_row
            block_col = from_col + col_diff // 2
            
        return self.board[block_row][block_col] is None
        
    def is_valid_chariot_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate chariot movement"""
        # Must move orthogonally
        if from_row != to_row and from_col != to_col:
            return False
            
        # Check if path is clear
        if from_row == to_row:
            # Horizontal movement
            start_col = min(from_col, to_col) + 1
            end_col = max(from_col, to_col)
            for col in range(start_col, end_col):
                if self.board[from_row][col] is not None:
                    return False
        else:
            # Vertical movement
            start_row = min(from_row, to_row) + 1
            end_row = max(from_row, to_row)
            for row in range(start_row, end_row):
                if self.board[row][from_col] is not None:
                    return False
                    
        return True
        
    def is_valid_cannon_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate cannon movement"""
        # Must move orthogonally
        if from_row != to_row and from_col != to_col:
            return False
            
        target_piece = self.board[to_row][to_col]
        pieces_between = 0
        
        if from_row == to_row:
            # Horizontal movement
            start_col = min(from_col, to_col) + 1
            end_col = max(from_col, to_col)
            for col in range(start_col, end_col):
                if self.board[from_row][col] is not None:
                    pieces_between += 1
        else:
            # Vertical movement
            start_row = min(from_row, to_row) + 1
            end_row = max(from_row, to_row)
            for row in range(start_row, end_row):
                if self.board[row][from_col] is not None:
                    pieces_between += 1
                    
        # If capturing, need exactly one piece between
        # If not capturing, need no pieces between
        if target_piece:
            return pieces_between == 1
        else:
            return pieces_between == 0
            
    def is_valid_pawn_move(self, from_row: int, from_col: int, to_row: int, to_col: int) -> bool:
        """Validate pawn movement"""
        piece = self.board[from_row][from_col]
        row_diff = to_row - from_row
        col_diff = abs(to_col - from_col)
        
        # Can only move one step
        if abs(row_diff) + col_diff != 1:
            return False
            
        if piece.player == Player.RED:
            # Red pawns move upward (decreasing row numbers)
            if row_diff > 0:
                return False
            # Can move sideways only after crossing river
            if col_diff == 1 and from_row > 4:
                return False
        else:
            # Black pawns move downward (increasing row numbers)
            if row_diff < 0:
                return False
            # Can move sideways only after crossing river
            if col_diff == 1 and from_row < 5:
                return False
                
        return True
        
    def make_move(self, from_row: int, from_col: int, to_row: int, to_col: int):
        """Execute a move on the board"""
        piece = self.board[from_row][from_col]
        captured_piece = self.board[to_row][to_col]
        
        # Move the piece
        self.board[to_row][to_col] = piece
        self.board[from_row][from_col] = None
        piece.row = to_row
        piece.col = to_col
        
        # Check for game over
        if captured_piece and captured_piece.piece_type == PieceType.GENERAL:
            self.game_over = True
            winner = "Red" if piece.player == Player.RED else "Black"
            QMessageBox.information(self, "Game Over", f"{winner} wins!")
        
        # Switch turns
        self.current_player = Player.BLACK if self.current_player == Player.RED else Player.RED
        
        # Deselect piece
        self.selected_piece = None
        self.selected_pos = None
        
        # Emit signal for move logging
        self.piece_moved.emit(from_row, from_col, to_row, to_col)
        
        self.update()
        
    def reset_game(self):
        """Reset the game to initial state"""
        self.setup_initial_board()


class MainWindow(QMainWindow):
    """Main window for the Chinese chess game"""
    
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle("Chinese Chess (Xiangqi)")
        self.setGeometry(100, 100, 1000, 750)
        
        # Create menu bar
        self.create_menu_bar()
        
        # Create status bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("Red's turn")
        
        # Create central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Main layout with splitter
        layout = QHBoxLayout()
        splitter = QSplitter(Qt.Orientation.Horizontal)
        
        # Chess board
        self.chess_board = ChessBoard()
        self.chess_board.piece_moved.connect(self.on_piece_moved)
        splitter.addWidget(self.chess_board)
        
        # Right panel for game info
        right_panel = QWidget()
        right_layout = QVBoxLayout()
        
        # Game info
        info_group = QGroupBox("Game Information")
        info_layout = QVBoxLayout()
        
        self.current_player_label = QLabel("Current Player: Red")
        self.current_player_label.setFont(QFont("Arial", 12, QFont.Weight.Bold))
        info_layout.addWidget(self.current_player_label)
        
        info_group.setLayout(info_layout)
        right_layout.addWidget(info_group)
        
        # Game controls
        controls_group = QGroupBox("Game Controls")
        controls_layout = QVBoxLayout()
        
        self.new_game_button = QPushButton("New Game")
        self.new_game_button.clicked.connect(self.new_game)
        controls_layout.addWidget(self.new_game_button)
        
        controls_group.setLayout(controls_layout)
        right_layout.addWidget(controls_group)
        
        # Move history
        history_group = QGroupBox("Move History")
        history_layout = QVBoxLayout()
        
        self.move_history = QTextEdit()
        self.move_history.setReadOnly(True)
        self.move_history.setMaximumHeight(300)
        history_layout.addWidget(self.move_history)
        
        history_group.setLayout(history_layout)
        right_layout.addWidget(history_group)
        
        # Game rules
        rules_group = QGroupBox("Quick Rules")
        rules_layout = QVBoxLayout()
        
        rules_text = QLabel("""
帥/將: Moves 1 step orthogonally within palace
仕/士: Moves 1 step diagonally within palace  
相/象: Moves 2 steps diagonally, can't cross river
馬/马: Moves like knight, can be blocked
車/车: Moves orthogonally any distance
炮/砲: Like rook but jumps to capture
兵/卒: Moves forward, sideways after crossing river
        """)
        rules_text.setWordWrap(True)
        rules_text.setFont(QFont("Arial", 8))
        rules_layout.addWidget(rules_text)
        
        rules_group.setLayout(rules_layout)
        right_layout.addWidget(rules_group)
        
        right_panel.setLayout(right_layout)
        right_panel.setMaximumWidth(300)
        splitter.addWidget(right_panel)
        
        layout.addWidget(splitter)
        central_widget.setLayout(layout)
        
    def create_menu_bar(self):
        """Create the menu bar"""
        menubar = self.menuBar()
        
        # Game menu
        game_menu = menubar.addMenu('Game')
        
        new_game_action = QAction('New Game', self)
        new_game_action.setShortcut('Ctrl+N')
        new_game_action.triggered.connect(self.new_game)
        game_menu.addAction(new_game_action)
        
        game_menu.addSeparator()
        
        exit_action = QAction('Exit', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        game_menu.addAction(exit_action)
        
        # Help menu
        help_menu = menubar.addMenu('Help')
        
        about_action = QAction('About', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)
        
    def on_piece_moved(self, from_row: int, from_col: int, to_row: int, to_col: int):
        """Handle piece movement"""
        # Convert coordinates to chess notation
        from_pos = f"{chr(ord('a') + from_col)}{10 - from_row}"
        to_pos = f"{chr(ord('a') + to_col)}{10 - to_row}"
        
        current_player = "Black" if self.chess_board.current_player == Player.RED else "Red"
        move_text = f"{current_player}: {from_pos} → {to_pos}"
        
        self.move_history.append(move_text)
        
        # Update current player display
        player_name = "Red" if self.chess_board.current_player == Player.RED else "Black"
        self.current_player_label.setText(f"Current Player: {player_name}")
        self.status_bar.showMessage(f"{player_name}'s turn")
        
    def new_game(self):
        """Start a new game"""
        self.chess_board.reset_game()
        self.move_history.clear()
        self.current_player_label.setText("Current Player: Red")
        self.status_bar.showMessage("Red's turn - New game started")
        
    def show_about(self):
        """Show about dialog"""
        QMessageBox.about(
            self,
            "About Chinese Chess",
            "Chinese Chess (Xiangqi) Game\n\n"
            "A complete implementation of traditional Chinese chess.\n\n"
            "Features:\n"
            "• Complete rule set for all pieces\n"
            "• Turn-based gameplay\n"
            "• Move validation\n"
            "• Win condition detection\n"
            "• Move history tracking\n\n"
            "Click on a piece to select it, then click on a valid destination to move."
        )


def main():
    """Main function to run the Chinese chess game"""
    app = QApplication(sys.argv)
    
    # Set application properties
    app.setApplicationName("Chinese Chess")
    app.setApplicationVersion("1.0")
    app.setOrganizationName("Chess Games")
    
    # Create and show main window
    window = MainWindow()
    window.show()
    
    # Run the application
    sys.exit(app.exec())


if __name__ == "__main__":
    main() 